package org.mysofts.eclipseman.archives;

import java.io.File;
import java.io.IOException;
import java.util.Collection;

import org.apache.commons.io.FileUtils;
import org.mysofts.eclipseman.util.AppLog;
import org.mysofts.eclipseman.util.MessageProvider;

/**
 * Archive abstraite.
 * 
 * @author reda
 * 
 */
public abstract class Archive { // $codepro.audit.disable
								// declareDefaultConstructors
	// [start] static fields
	/**
	 * Valeur de l'attribut {@link #this.checksum} quand le checksum de
	 * l'archive est inconnu.
	 */
	private static final int UNKOWN_CHECKSUM = -1;
	// [end]

	// [start] fields
	/**
	 * Extension du type d'archive gérées par cette commande (sans le point).
	 */
	private final String extension;
	/**
	 * Fichier de l'archive.
	 */
	private final File file;
	/**
	 * Somme CRC32 du fichier archive.
	 */
	private long checksum = UNKOWN_CHECKSUM;

	// [end]

	// [start] constructors
	/**
	 * Construis cet objet.
	 * 
	 * @param pExtension
	 *            Extension des archives gérées par cette classe.
	 * @param pArchive
	 *            Archive gérée par cette instance.
	 */
	protected Archive(final String pExtension, final File pArchive) {
		super();
		this.extension = pExtension.toLowerCase();
		this.file = pArchive;
	}

	// [end]

	// [start] regular getters
	/**
	 * Retourne l'extension du type d'archive gérées par cette commande (sans le
	 * point).
	 * 
	 * @return Extension du type d'archive gérées par cette commande (sans le
	 *         point).
	 */
	public final String getExtension() {
		return this.extension;
	}

	/**
	 * Retourne le fichier archive géré par cette instance.
	 * 
	 * @return Fichier archive géré par cette instance.
	 */
	public final File getArchiveFile() {
		return this.file;
	}

	// [end]

	// [start] abstract
	/**
	 * Archive un fichier ou un dossier, vers le fichier archive géré par cette
	 * instance.
	 * 
	 * @param fileToArchive
	 *            fichier/dossier à archiver.
	 * @throws IOException
	 *             Si l'archivage a échoué.
	 */
	public abstract void archive(final File fileToArchive) throws IOException;

	/**
	 * Extrait l'archive.
	 * 
	 * @param extractTo
	 *            Dossier où sera extraite l'archive.
	 * @throws IOException
	 *             Si l'extraction a échouée.
	 * 
	 * @return Liste des dossier racines extraits de l'archive.
	 */
	public abstract Collection<File> extract(final File extractTo)
			throws IOException;

	/**
	 * Liste les fichiers dans l'archive.
	 * 
	 * @return Liste de fichiers dans l'archive.
	 * @throws IOException
	 *             Erreur d'execution de la commande de listage de fichiers.
	 */
	public abstract Collection<String> listFiles() throws IOException;

	/**
	 * Crée une nouvelle instance de l'archive.
	 * 
	 * @param file
	 *            Fichier archive.
	 * @return Nouvel objet créé.
	 */
	protected abstract Archive instanciate(File file);

	// [end]

	// [start] methods
	/**
	 * Extrait l'archive dans son dossier parent.
	 * 
	 * @return Liste des dossier racines extraits de l'archive.
	 * @throws IOException
	 *             Si l'extraction a échouée.
	 */
	public final Collection<File> extract() throws IOException {
		return this.extract(this.getArchiveFile().getParentFile());
	}

	/**
	 * Retourne le checksum CRC32 du fichier archive, le checksum n'est calculé
	 * qu'au premier appel de cette méthode, il est mémorisé ensuite durant
	 * toute la durée de vie de cette instance.
	 * 
	 * @return Checksum CRC32 du fichier.
	 * @throws IOException
	 *             Si le fichier archive n'existe pas ou une erreur a empêché le
	 *             calcul.
	 */
	public final long checksum() throws IOException {
		if (this.checksum == UNKOWN_CHECKSUM) {
			this.checksum = FileUtils.checksumCRC32(this.getArchiveFile());
		} // end if

		return this.checksum;
	}

	/**
	 * Vérifie que le fichier archive géré par cette instance possède une bonne
	 * extension.
	 * 
	 * @return {@code true} si l'extension est correcte; {@code false} sinon.
	 */
	public final boolean isVaidFileExtension() {
		final String name = getArchiveFile().getName().toLowerCase();

		return name.endsWith("." + getExtension()); // $codepro.audit.disable
													// disallowStringConcatenation
	}

	/**
	 * Retourne le nom simple (sans extension) du fichier archive que gére cette
	 * instance.
	 * 
	 * @return Nom simple de l'archive.
	 */
	public final String simpleName() {
		final String fullname = this.file.getName();
		String result;
		if (this.isVaidFileExtension()) {
			result = fullname.substring(0,
					fullname.length() - this.extension.length() - 1);
		} else {
			final int i = fullname.lastIndexOf('.');
			result = fullname.substring(0, i);
		} // end if

		return result;
	}

	/**
	 * Vérifie si un fichier archive est considéré comme similaire à l'archive
	 * gérée par cette instance. <br>
	 * Deux fichiers archive sont considérés comme similaires ssi une des
	 * conditions suivantes est vérifiée:
	 * <ol>
	 * <li>Les deux possèdent le même nom simple {@see #simpleName()}</li>
	 * <li>Les deux possèdent le même checksum {@see #checksum()}.</li>
	 * </ol>
	 * Les conditions citées sont vérifiées séquentiellement dans l'ordre, la
	 * première condition vraie met fin à la vérification avec un retour
	 * {@code true}, si aucune des conditions n'est vérifiée, la méthode renvoie
	 * {@code false}.
	 * 
	 * @param otherArchive
	 *            Autre archive.
	 * @return {@code true} si les archives sont similaires, {@code false}
	 *         sinon.
	 */
	public final boolean similarTo(final Archive otherArchive) { // $codepro.audit.disable
																	// booleanMethodNamingConvention
		if (otherArchive != null) {
			if (this.simpleName().equals(otherArchive.simpleName())) {
				return true;
			} else {
				try {
					final long checksum1 = this.checksum();
					final long checksum2 = otherArchive.checksum();

					return checksum1 == checksum2;
				} catch (IOException e) {
					AppLog.error(MessageProvider.get("logs").sub("archives")
							.sub("checksum").sub("error").value(), e);
					return false;
				}
			} // end if
		} // end if

		return false;
	}

	/**
	 * Copie l'archive vers une nouvelle location.
	 * 
	 * @param newlocation
	 *            Nouveau fichier de destination de la copie.
	 * @return Nouveau manager de l'archive copiée.
	 * @throws IOException
	 *             Erreur de copie.
	 */
	public final Archive copyToOtherLocation(final File newlocation)
			throws IOException {
		FileUtils.copyFile(this.file, newlocation);
		return this.instanciate(newlocation);
	}

	// [end]

	// [start] object implementation
	@Override
	public final int hashCode() {
		final int prime = 31; // $codepro.audit.disable numericLiterals
		int result = 1;
		int filehash = 0;
		if (this.file != null) {
			filehash = this.file.hashCode();
		} // end if
		result = prime * result + filehash;
		return result;
	}

	@Override
	public final boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		} // end if
		if (obj == null) {
			return false;
		} // end if
		if (!(obj instanceof Archive)) { // $codepro.audit.disable
											// disallowInstanceof
			return false;
		} // end if
		final Archive other = (Archive) obj;
		if (this.file == null) {
			if (other.file != null) {
				return false;
			} // end if
		} else if (!this.file.equals(other.file)) {
			return false;
		} // end if
		return true;
	}

	@Override
	public final String toString() {
		return "Archive Object [type=" + this.getClass().getSimpleName()
				+ ", file=" + this.file + "]";
	}
	// [end]
}